﻿using BingSpatialDataServiceExcelAddin.BSDSTools;
using BingSpatialDataServiceExcelAddin.BSDSTools.Models;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using Excel = Microsoft.Office.Interop.Excel;

namespace BingSpatialDataServiceExcelAddin.Views
{
    /// <summary>
    /// Interaction logic for UploadPanel.xaml
    /// </summary>
    public partial class UploadPanel : UserControl
    {
        #region Private Methods

        private Settings bsdsSettings;
        private StringBuilder log;

        #endregion

        #region Constructor

        public UploadPanel(System.Windows.Forms.Form parent)
        {
            InitializeComponent();

            ParentForm = parent;

            log = new StringBuilder();

            bsdsSettings = new Settings(Globals.ThisAddIn.Application.ActiveWorkbook);

            if (string.IsNullOrWhiteSpace(bsdsSettings.Culture))
            {
                bsdsSettings.Culture = "en-US";
            }

            DSNameTbx.Text = bsdsSettings.DataSourceName;
            EntityTypeTbx.Text = bsdsSettings.EntityTypeName;
            MasterKeyTbx.Text = bsdsSettings.MasterKey;
            QueryKeyTbx.Text = bsdsSettings.QueryKey;
            MakePublicChbx.IsChecked = bsdsSettings.IsPublic;
        }

        #endregion

        public System.Windows.Forms.Form ParentForm { get; set; }

        #region Button Handlers

        private void UploadBtn_Click(object sender, RoutedEventArgs e)
        {
            bsdsSettings.DataSourceName = DSNameTbx.Text;
            bsdsSettings.EntityTypeName = EntityTypeTbx.Text;
            bsdsSettings.MasterKey = MasterKeyTbx.Text;
            bsdsSettings.QueryKey = QueryKeyTbx.Text;
            bsdsSettings.IsPublic = MakePublicChbx.IsChecked.Value;

            bool skipGeocoding = (SkipGeocodingChbx.IsChecked.HasValue && SkipGeocodingChbx.IsChecked.Value)? true : false;

            if (string.IsNullOrWhiteSpace(bsdsSettings.DataSourceName))
            {
                MessageBox.Show("Invalid Data Source Name Entry.");
                return;
            }

            if (string.IsNullOrWhiteSpace(bsdsSettings.EntityTypeName))
            {
                MessageBox.Show("Invalid Entity Type Name Entry.");
                return;
            }

            if (string.IsNullOrWhiteSpace(bsdsSettings.MasterKey))
            {
                MessageBox.Show("Invalid Master Key Entry.");
                return;
            }

            //Save Settings
            bsdsSettings.Save(Globals.ThisAddIn.Application.ActiveWorkbook);

            //Check to see if file needs geocoding
            if (!skipGeocoding)
            {
                var sheet = (Excel.Worksheet)Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet;

                bool isValid, includesSchemaInfo;
                string notes = "";
                BSDSTools.FileTools.ColumnHeader header;

                DataSourceTools.ValidateDataSource(sheet, out isValid, out includesSchemaInfo, out notes, out header);

                if (!isValid)
                {
                    MessageBox.Show("Data Source Invalid.\r\n" + notes);
                    ParentForm.Close();
                }
                else
                {
                    Excel.Range worksheetCells = DataSourceTools.GetDataRange(sheet, includesSchemaInfo ? "A2" : "A1");
                    object[,] data = worksheetCells.Value2 as object[,];

                    List<GeocodeFeedGeocodeEntity> entities = new List<GeocodeFeedGeocodeEntity>();

                    int idIdx = header.GetPrimaryKeyIndex() + 1;
                    int latIdx = header.GetIndex("Latitude") + 1;
                    int lonIdx = header.GetIndex("Longitude") + 1;

                    int addIdx = header.GetIndex("AddressLine") + 1;
                    int locIdx = header.GetIndex("Locality") + 1;
                    int pcIdx = header.GetIndex("PostalCode") + 1;
                    int adIdx = header.GetIndex("AdminDistrict") + 1;
                    int crIdx = header.GetIndex("CountryRegion") + 1;

                    //TODO: AdminDistrict2, Query, FormattedAddress

                    if (addIdx == 0 && locIdx == 0 && pcIdx == 0 && adIdx == 0 && crIdx == 0)
                    {
                        MessageBox.Show("Unable to find any valid address columns:\r\nAddressLine, Locality, PostalCode, AdminDistrict, CountryRegion");
                        ParentForm.Close();
                        return;
                    }

                    int numRows = data.GetLength(0);

                    for (int i = 2; i <= numRows; i++)
                    {
                        //Only batch geocode entities that have no values or both lat and lon are 0
                        if (data[i, latIdx] == null || data[i, lonIdx] == null ||
                            (double.Parse(data[i, latIdx].ToString()) == 0 && double.Parse(data[i, lonIdx].ToString()) == 0))
                        {
                            entities.Add(new GeocodeFeedGeocodeEntity()
                            {
                                Id = i.ToString(),
                                GeocodeRequest = new GeocodeFeedGeocodeEntityGeocodeRequest()
                                {
                                    Culture = bsdsSettings.Culture,
                                    Address = new GeocodeFeedGeocodeEntityGeocodeRequestAddress()
                                    {
                                        AddressLine = DataSourceTools.SafeStringValueCast(data, i, addIdx),
                                        Locality = DataSourceTools.SafeStringValueCast(data, i, locIdx),
                                        PostalCode = DataSourceTools.SafeStringValueCast(data, i, pcIdx),
                                        AdminDistrict = DataSourceTools.SafeStringValueCast(data, i, adIdx),
                                        CountryRegion = DataSourceTools.SafeStringValueCast(data, i, crIdx)
                                    }
                                }
                            });
                        }
                    }

                    if (entities.Count > 0)
                    {
                        GeocodeFeed feed = new GeocodeFeed()
                        {
                            GeocodeEntity = entities.ToArray()
                        };

                        BatchGeocoder geocoder = new BatchGeocoder();

                        geocoder.GeocodeCompleted += (x) =>
                        {
                            if (!string.IsNullOrWhiteSpace(x.Error))
                            {
                                MessageBox.Show(x.Error);
                            }
                            else
                            {
                                List<string> ungeocodedIds = new List<string>();

                                log.AppendFormat("Batch Geocode Job completed in {0}", ElapsedTimer.Text);

                                if (x.Successful != null && x.Successful.GeocodeEntity != null && x.Successful.GeocodeEntity.Length > 0)
                                {
                                    log.AppendFormat("\r\nSuccessfully geocoded {0} entities.\r\n", x.Successful.GeocodeEntity.Length);

                                    int row;
                                    foreach (GeocodeFeedGeocodeEntity entity in x.Successful.GeocodeEntity)
                                    {
                                        row = int.Parse(entity.Id);

                                        if (entity.GeocodeResponse != null && entity.GeocodeResponse.Length > 0)
                                        {
                                            worksheetCells.Cells.set_Item(row, latIdx, entity.GeocodeResponse[0].Point.Latitude);
                                            worksheetCells.Cells.set_Item(row, lonIdx, entity.GeocodeResponse[0].Point.Longitude);
                                        }
                                        else
                                        {
                                            ungeocodedIds.Add(DataSourceTools.SafeStringValueCast(data, row, addIdx));
                                        }
                                    }
                                }

                                int totalFailed = ungeocodedIds.Count;

                                if (x.Failed != null && x.Failed.GeocodeEntity != null && x.Failed.GeocodeEntity.Length > 0)
                                {
                                    totalFailed += x.Failed.GeocodeEntity.Length;
                                }

                                if (totalFailed > 0)
                                {
                                    log.AppendFormat("\r\nFailed to geocode {0} entities.\r\n", totalFailed);
                                    log.Append("Primary Key: Fault Reason\r\n");

                                    int row;
                                    string id;

                                    if (x.Failed != null && x.Failed.GeocodeEntity != null)
                                    {
                                        foreach (GeocodeFeedGeocodeEntity entity in x.Failed.GeocodeEntity)
                                        {
                                            row = int.Parse(entity.Id);
                                            id = DataSourceTools.SafeStringValueCast(data, row, addIdx);

                                            log.AppendFormat("{0}: {1}\r\n", id, entity.FaultReason);
                                        }
                                    }

                                    foreach (string i in ungeocodedIds)
                                    {
                                        log.AppendFormat("{0}: Address not found.\r\n", i);
                                    }
                                }

                                //Upload data source
                                Upload(bsdsSettings);
                            }
                        };

                        geocoder.ProgressChanged += (x) =>
                        {
                            Status.Text = x;
                        };

                        geocoder.Geocode(feed, bsdsSettings.MasterKey);

                        ElapsedTimer.IsStarted = true;
                    }
                    else
                    {
                        ElapsedTimer.IsStarted = true;

                        //All Entities already Geocoded
                        Upload(bsdsSettings);
                    }
                }
            }
            else
            {
                ElapsedTimer.IsStarted = true;

                //Just Upload the data source
                Upload(bsdsSettings);
            }

            InputPanel.Visibility = System.Windows.Visibility.Collapsed;
            StatusPanel.Visibility = System.Windows.Visibility.Visible;
        }

        private void CancelBtn_Click(object sender, RoutedEventArgs e)
        {
            ParentForm.Close();
        }

        #endregion

        #region Private Methods

        //TODO: Consider storing Job info in settings and monitoring seperately.
        private void Upload(Settings settings)
        {
            DataSourceService uploader = new DataSourceService();

            uploader.UploadCompleted += (r) =>
            {
                ElapsedTimer.IsStarted = false;

                if (r != null){
                    if(r.ErrorDetails != null && r.ErrorDetails.Length > 0){
                        log.AppendLine(r.ErrorDetails[0]);
                        DisplayMessage("Error Uploading Data Source", log.ToString());
                    }
                    else if(r.ResourceSets != null
                        && r.ResourceSets.Length > 0
                        && r.ResourceSets[0] != null
                        && r.ResourceSets[0].Resources != null
                        && r.ResourceSets[0].Resources.Length > 0
                        && r.ResourceSets[0].Resources[0] != null)
                    {
                        DataflowJob job = r.ResourceSets[0].Resources[0];
                        bool aborted = false;
                        
                        if (r.ResourceSets[0].Resources[0].Status.Equals("Completed"))
                        {
                            log.AppendFormat("Upload Completed in {0}.\r\n", ElapsedTimer.Text);

                            foreach (Link l in job.Links)
                            {
                                if (l.Role.Equals("datasource") && l.Name != null && l.Name.Equals("service"))
                                {
                                    log.AppendFormat("Query URL: {0}\r\n", l.URL);
                                    break;
                                }
                            }
                        }
                        else if (r.ResourceSets[0].Resources[0].Status.Equals("Aborted"))
                        {
                            log.AppendLine("Upload Aborted.\r\n");
                            aborted = true;
                        }

                        log.AppendFormat("Total Entities{0}\r\n", job.TotalEntityCount);
                        log.AppendFormat("Processed Entities{0}\r\n", job.ProcessedEntityCount);

                        if(aborted){
                            log.AppendFormat("Failed Entities{0}\r\n", job.FailedEntityCount);

                            if (job.FailedEntityCount > 0 && job.Links != null)
                            {
                                Link failedLink = null;

                                foreach (Link l in job.Links)
                                {
                                    if(l.Role.Equals("output") && l.Name.Equals("failed")){
                                        failedLink = l;
                                        break;
                                    }
                                }

                                if (failedLink != null)
                                {
                                    //Download failed entity information and add to output.
                                    WebClient wc = new WebClient();
                                    string failedInfo = wc.DownloadString(failedLink.URL + "?key=" + settings.MasterKey);

                                    //TODO: Output Failed Info
                                    log.Append(failedInfo);
                                }
                            }
                        }

                        DisplayMessage("Upload Log", log.ToString());
                    }
                }

                ParentForm.Close();
            };

            uploader.ProgressChanged += (x) => {
                Status.Text = x;
            };

            uploader.Upload(settings);
        }

        private void DisplayMessage(string title, string msg)
        {
            OutputModal form = new OutputModal(title, msg);
            System.Windows.Forms.NativeWindow xlMain = new System.Windows.Forms.NativeWindow();
            xlMain.AssignHandle(new IntPtr(Globals.ThisAddIn.Application.Hwnd));

            form.ShowDialog(xlMain);
            form.Dispose();
            xlMain.ReleaseHandle();
        }

        #endregion
    }
}
